package Stana.kmeansSeqPatternMining;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.StringTokenizer;

import Stana.hierarchicalClustering.PriceQuantityCluster;
import Stana.hierarchicalClustering.PriceQuantityItem;



public class AlgoKMeansSeq extends AbstractAlgoClustering {

	private int k;
	private final static Random random = new Random(System.currentTimeMillis());
	private String inputFile = "";
	private String outputFile = "";
	List<PriceQuantityItem> items;


	public AlgoKMeansSeq(String inputFile, String outputFile) {
		this.inputFile = inputFile;
		this.outputFile = outputFile;
		items = new ArrayList<PriceQuantityItem>();
		// open the file and read
		try {
			// Open the file that is the first 
			// command line parameter
			FileInputStream fstream = new FileInputStream(inputFile);
			// Get the object of DataInputStream
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			//Read File Line By Line
			while ((strLine = br.readLine()) != null)   {
				  StringTokenizer data = new StringTokenizer(strLine,",");
				  String prodName = "";
				  String salesDate = "";
				  float costQuantity = 0.0f;
				  float price = 0.0f;

				  while (data.hasMoreElements()) {
						prodName = (String)data.nextElement();
						salesDate = (String)data.nextElement();
						costQuantity = Float.parseFloat((String)data.nextElement());
						price = Float.parseFloat((String)data.nextElement());
				  }
				  PriceQuantityItem item = new PriceQuantityItem(
						  prodName, salesDate, costQuantity, price);
				items.add(item);
				
			}
	  //Close the input stream
	  	in.close();
      } catch (Exception e){//Catch exception if any
		System.err.println("Error: " + e.getMessage());
	  }		
	}
	public List<PriceQuantityItem> getPriceQuantityItems() {
		return items;
	}

	public void writeOutput(List<PriceQuantityCluster> clusters2) {
		FileOutputStream fstream;
		try {
		  fstream = new FileOutputStream(outputFile);
		  // Get the object of DataInputStream
		  DataOutputStream out = new DataOutputStream(fstream);
		  BufferedWriter br = new BufferedWriter(new OutputStreamWriter(out));
			for(PriceQuantityCluster cluster : clusters2){
				// Write the output in the file also
				br.write(cluster.toString()+"\r\n");
			}
			br.close();
			out.close();
			fstream.close();
		} catch (Exception e) {
			System.out.println(" the Exception occurred while write output in AlgoKMeans");
			System.out.println(e.getMessage());
		}
		
	}
	
	
	/**
	 * 
	 * @param input
	 * @param k
	 *            : le nombre de clusters
	 * @return
	 */
	public List<PriceQuantityCluster> runAlgorithm(List<PriceQuantityItem> input) {

		List<PriceQuantityCluster> clusters = new ArrayList<PriceQuantityCluster>();
		// Cas particulier : 1 seul item
		if (input.size() == 1) {
			PriceQuantityItem item = input.get(0);
			PriceQuantityCluster cluster = new PriceQuantityCluster(item);
			cluster.addItem(item);
			clusters.add(cluster);
			return clusters;
		} else {
			for (PriceQuantityItem item : input) {
				PriceQuantityCluster cluster = new PriceQuantityCluster(item);
				cluster.addItem(item);
				clusters.add(cluster);
			}
		}

		// (1) Randomly generate k empty clusters with a random median (cluster
		// center)

		// (1.1) Choose the higher and lower values for generating a medianQuantity
		double higherQuantity = input.get(0).getQuantity();
		double lowerQuantity = input.get(0).getQuantity();
		for (PriceQuantityItem item : input) {
			if (item.getQuantity() > higherQuantity) {
				higherQuantity = item.getQuantity();
			}
			if (item.getQuantity() < lowerQuantity) {
				lowerQuantity = item.getQuantity();
			}
		}
		double higherPrice = input.get(0).getPrice();
		double lowerPrice = input.get(0).getPrice();
		for (PriceQuantityItem item : input) {
			if (item.getPrice() > higherPrice) {
				higherPrice = item.getPrice();
			}
			if (item.getPrice() < lowerPrice) {
				lowerPrice = item.getPrice();
			}
		}
		// Special case : all items have the same values, so we return only one
		// cluster.
		if ((higherPrice == lowerPrice)  && (higherQuantity == lowerQuantity)) {
			PriceQuantityCluster cluster = new PriceQuantityCluster(input);
			clusters.add(cluster);
			return clusters;
		}

		// (1.2) Generate the k empty clusters with random median
		for (int i = 0; i < k; i++) {
			// generate random median
			double medianQuantity = random.nextInt((int) (higherQuantity - lowerQuantity)) + lowerQuantity;
			double medianPrice = random.nextInt((int) (higherPrice - lowerPrice)) + lowerPrice;
			// create the cluster
			PriceQuantityCluster cluster = new PriceQuantityCluster(medianPrice, medianQuantity);
			clusters.add(cluster);
		}

		// (2) Repeat the two next steps until the assignment hasn't changed
		boolean changed;

		do {
			changed = false;
			// (2.1) Assign each point to the nearest cluster center.

			// / for each item
			for (PriceQuantityItem item : input) {
				// find the nearest cluster and the cluster containing the item
				PriceQuantityCluster nearestCluster = null;
				PriceQuantityCluster containingCluster = null;
				double priceToNearestCluster = Double.MAX_VALUE;
				double quantityToNearestCluster = Double.MAX_VALUE;

				for (PriceQuantityCluster cluster : clusters) {
					double quantity = medianQuantity(cluster, item);
					if (quantity < priceToNearestCluster) {
						nearestCluster = cluster;
						priceToNearestCluster = quantity;
					}
					if (cluster.containsItem(item)) {
						containingCluster = cluster;
					}
				}

				for (PriceQuantityCluster cluster : clusters) {
					double price = medianPrice(cluster, item);
					if (price < priceToNearestCluster) {
						nearestCluster = cluster;
						priceToNearestCluster = price;
					}
					if (cluster.containsItem(item)) {
						containingCluster = cluster;
					}
				}

				if (containingCluster != nearestCluster) {
					if (containingCluster != null) {
						removeItem(containingCluster.getItems(), item);
						// fixed 2010 because before I was using "remove" from
						// List but ItemValued defines "equals".
					}
					nearestCluster.addItem(item);
					changed = true;
				}
			}
			// (2.2) Recompute the new cluster medians
			for (PriceQuantityCluster cluster : clusters) {
				
				cluster.recomputeClusterMedianPrice();
				cluster.recomputeClusterMedianQuantity();
			
			}

		} while (changed);

		// Computer min and max for all clusters
		for (PriceQuantityCluster cluster : clusters) {
			cluster.computeHigherAndLowerPrice();
			cluster.computeHigherAndLowerQuantity();
		}
		return clusters;
	}

	private void removeItem(List<PriceQuantityItem> items, PriceQuantityItem item) {
		for (int i = 0; i < items.size(); i++) {
			if (items.get(i) == item) {
				items.remove(i);
			}
		}
	}

	private double medianPrice(PriceQuantityCluster cluster1, PriceQuantityItem item) {
		return Math.abs(cluster1.getMedianPrice() - item.getPrice());
	}
	
	private double medianQuantity(PriceQuantityCluster cluster1, PriceQuantityItem item) {
		return Math.abs(cluster1.getMedianQuantity() - item.getQuantity());
	}

	public void setK(int k) {
		this.k = k;
	}

}
